﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;

namespace Jack.EntityFrameworkCore
{
    public class EfCoreUnitOfWork<TDbContext> : IEfCoreUnitOfWork<TDbContext>, IUnitOfWork, IDisposable, IDbConnection
        where TDbContext : JackDbContext<TDbContext>
    {
        private readonly TDbContext _context;
        private readonly Lazy<IDbConnection> _connection;
        //private IDbContextTransaction _dbContextTransaction;
        //private Lazy<System.Transactions.TransactionScope> _transactionScope;
        private bool _hasSaveChangesFailed = false;
        //private IDbTransaction _dbTransaction;
        private Lazy<TransactionManager> _transactionManager;

        public EfCoreUnitOfWork(TDbContext context)
        {
            _context = context;
            _connection = new Lazy<IDbConnection>(() => context.Database.GetDbConnection(), true);
            //_transactionScope = new Lazy<System.Transactions.TransactionScope>(() =>
            //{
            //    _context.SaveChangesFailed += _context_SaveChangesFailed;
            //    //IDbTransaction dbTransaction = _connection.Value.BeginTransaction();
            //    //System.Transactions.Transaction.Current.TransactionInformation
            //    //System.Transactions.Transaction.Current.TransactionInformation.Status

            //    DbTransaction dbTransaction = _context.Database.BeginTransaction().GetDbTransaction();
            //    //_context.Database.UseTransaction(dbTransaction);
            //    _dbTransaction = dbTransaction;

            //    return new System.Transactions.TransactionScope(System.Transactions.TransactionScopeAsyncFlowOption.Enabled);
            //});

            _transactionManager = new Lazy<TransactionManager>(() => TransactionManager.CreateInstance(_context));

            //RelationalDatabaseFacadeExtensions
            //_context.Database.
        }

        private void _context_SaveChangesFailed(object sender, SaveChangesFailedEventArgs e)
        {
            SetTransactionScopeFailed();
        }

        public TDbContext DbContext => _context;

        public string ConnectionString { get => _connection.Value.ConnectionString; set => _connection.Value.ConnectionString = value; }

        public int ConnectionTimeout => _connection.Value.ConnectionTimeout;

        public string Database => _connection.Value.Database;

        public ConnectionState State => _connection.Value.State;

        public async Task<IDbContextTransaction> BeginTransaction(CancellationToken cancellationToken = default)
        {
            //if (_dbContextTransaction != null) return _dbContextTransaction;
            //_dbContextTransaction = await _context.Database.BeginTransactionAsync(cancellationToken);

            //return _dbContextTransaction;
            return await _context.Database.BeginTransactionAsync(cancellationToken);
        }

        public void OpenTransactionScope()
        {
            //_ = _transactionScope.Value;
            _ = _transactionManager.Value;
        }

        public void SetTransactionScopeFailed()
        {
            _hasSaveChangesFailed = true;
        }

        public async Task TransactionDecorator(Func<IDbTransaction, Task> func)
        {
            try
            {
                await func(_transactionManager.Value.DbTransaction).ConfigureAwait(false);
            }
            catch (Exception)
            {
                SetTransactionScopeFailed();
                throw;
            }
        }

        public void TransactionDecorator(Action<IDbTransaction> action)
        {
            try
            {
                action(_transactionManager.Value.DbTransaction);
            }
            catch (Exception)
            {
                SetTransactionScopeFailed();
                throw;
            }
        }

        public IDbTransaction BeginTransaction()
        {
            return _connection.Value.BeginTransaction();
        }

        public IDbTransaction BeginTransaction(IsolationLevel il)
        {
            return _connection.Value.BeginTransaction(il);
        }

        public void ChangeDatabase(string databaseName)
        {
            _connection.Value.ChangeDatabase(databaseName);
        }

        public void SetConnectionString(string connectionString) => _context.Database.SetConnectionString(connectionString);

        public string GetConnectionString() => _context.Database.GetConnectionString();

        public void Close()
        {
            _connection.Value.Close();
        }

        public IDbCommand CreateCommand()
        {
            return _connection.Value.CreateCommand();
        }

        //public async Task Complete(CancellationToken cancellationToken = default)
        //{
        //    if (_dbContextTransaction == null) return;
        //    await _dbContextTransaction.CommitAsync(cancellationToken);
        //    await _dbContextTransaction.DisposeAsync();
        //    _dbContextTransaction = null;
        //}

        public void Dispose()
        {
            //if (_dbContextTransaction != null)
            //{
            //    _dbContextTransaction.Commit();
            //    _dbContextTransaction.Dispose();
            //    _dbContextTransaction = null;
            //}

            //if (_transactionScope.IsValueCreated)
            //{
            //    if (_hasSaveChangesFailed == false)
            //    {
            //        try
            //        {
            //            _context.ChangeTracker.DetectChanges();
            //            if (_context.ChangeTracker.HasChanges())
            //            {
            //                _context.SaveChanges();
            //            }
            //        }
            //        catch (Exception)
            //        {
            //            _hasSaveChangesFailed = true;
            //        }
            //    }

            //    if (_hasSaveChangesFailed == false)
            //        _transactionScope.Value.Complete();
            //    _transactionScope.Value.Dispose();
            //}

            if (_transactionManager.IsValueCreated)
            {
                if (_hasSaveChangesFailed == false)
                {
                    try
                    {
                        _context.ChangeTracker.DetectChanges();
                        if (_context.ChangeTracker.HasChanges())
                        {
                            _context.SaveChanges();
                        }
                    }
                    catch (Exception)
                    {
                        _hasSaveChangesFailed = true;
                    }
                }

                if (_hasSaveChangesFailed == false)
                    _transactionManager.Value.Commit();
                else
                    _transactionManager.Value.Rollback();
                _transactionManager.Value.Dispose();
            }

            //_context.Dispose(); // 留给IOC容器自己释放，避免同一个上下文释放时，其它的工作单元对象可能还要用
        }

        public async ValueTask DisposeAsync()
        {
            //if (_dbContextTransaction != null)
            //{
            //    await _dbContextTransaction.CommitAsync();
            //    await _dbContextTransaction.DisposeAsync();
            //    _dbContextTransaction = null;
            //}

            //if (_transactionScope.IsValueCreated)
            //{
            //    if (_hasSaveChangesFailed == false)
            //    {
            //        try
            //        {
            //            _context.ChangeTracker.DetectChanges();
            //            if (_context.ChangeTracker.HasChanges())
            //            {
            //                await _context.SaveChangesAsync();
            //            }
            //        }
            //        catch (Exception)
            //        {
            //            _hasSaveChangesFailed = true;
            //        }
            //    }

            //    if (_hasSaveChangesFailed == false)
            //        _transactionScope.Value.Complete();
            //    _transactionScope.Value.Dispose();
            //}

            if (_transactionManager.IsValueCreated)
            {
                if (_hasSaveChangesFailed == false)
                {
                    try
                    {
                        _context.ChangeTracker.DetectChanges();
                        if (_context.ChangeTracker.HasChanges())
                        {
                            await _context.SaveChangesAsync();
                        }
                    }
                    catch (Exception)
                    {
                        _hasSaveChangesFailed = true;
                    }
                }

                if (_hasSaveChangesFailed == false)
                    await _transactionManager.Value.CommitAsync();
                else
                    await _transactionManager.Value.RollbackAsync();
                await _transactionManager.Value.DisposeAsync();
            }

            //await _context.DisposeAsync(); // 留给IOC容器自己释放，避免同一个上下文释放时，其它的工作单元对象可能还要用
        }

        public void Open()
        {
            _connection.Value.Open();
        }

        public void TryOpen()
        {
            if (_connection.Value.State != ConnectionState.Open)
                Open();
        }

        //public async Task Rollback(CancellationToken cancellationToken = default)
        //{
        //    if (_dbContextTransaction != null)
        //    {
        //        await _dbContextTransaction.RollbackAsync(cancellationToken);
        //        await _dbContextTransaction.DisposeAsync();
        //        _dbContextTransaction = null;
        //    }
        //}

        public async Task<int> SaveChanges(CancellationToken cancellationToken = default)
        {
            return await _context.SaveChangesAsync(cancellationToken);
        }

        public IRepository<TModel> CreateRepository<TModel>()
            where TModel : class, IBaseModel, new()
        {
            return _context.GetService<IRepository<TModel>>();
        }

        public IEfCoreRepository<TModel> CreateEfCoreRepository<TModel>()
            where TModel : class, IBaseModel, new()
        {
            return _context.GetService<IEfCoreRepository<TModel>>();
        }
    }
}
